/*
 *  Created at 2011-01-30 20:29 GMT-03:00
 *  Author: Victor Williams Stafusa da Silva
 *  JSmartUnit
 */
package org.jsmartunit.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.manipulation.Sorter;

/**
 * @author Victor Williams Stafusa da Silva
 */
public class TestOrder<T> {

    public static interface DescriptionFactory<T> {
        public Collection<? extends T> getUnorderedElements();
        public Description getDescription(T element);
    }

    private final Filter filter;
    private final Sorter sorter;
    private final DescriptionFactory<T> descriptedChildren;
    private final Comparator<? super T> comparator;

    public TestOrder(DescriptionFactory<T> descriptedChildren) {
        this(Filter.ALL, Sorter.NULL, descriptedChildren);
    }

    private TestOrder(Filter filter, Sorter sorter, DescriptionFactory<T> descriptedChildren) {
        this.filter = filter;
        this.sorter = sorter;
        this.descriptedChildren = descriptedChildren;
        this.comparator = createComparator();
    }

    public TestOrder<T> derive(Sorter sorter) {
        if (sorter == null) sorter = Sorter.NULL;
        return new TestOrder<T>(filter, sorter, descriptedChildren);
    }

    public TestOrder<T> derive(Filter filter) throws NoTestsRemainException {
        TestOrder<T> t = deriveSilently(filter);
        t.checkIfThereAreRemainingTests();
        return t;
    }

    public TestOrder<T> deriveSilently(Filter filter) {
        if (filter == null) filter = Filter.ALL;
        return new TestOrder<T>(filter, sorter, descriptedChildren);
    }

    private void checkIfThereAreRemainingTests() throws NoTestsRemainException {
        for (T each : descriptedChildren.getUnorderedElements()) {
            if (shouldRun(each)) return;
        }
        throw new NoTestsRemainException();
    }

    public List<T> getFilteredChildren() {
        List<T> filtered = new ArrayList<T>(descriptedChildren.getUnorderedElements().size());
        for (T each : descriptedChildren.getUnorderedElements()) {
            if (shouldRun(each)) {
                filterChild(each);
                sortChild(each);
                filtered.add(each);
            }
        }
        Collections.sort(filtered, comparator);
        return filtered;
    }

    private void sortChild(T child) {
        sorter.apply(child);
    }

    private void filterChild(T child) {
        try {
            filter.apply(child);
        } catch (NoTestsRemainException e) {
            // Skip it silently.
        }
    }

    private boolean shouldRun(T each) {
        return filter.shouldRun(descriptedChildren.getDescription(each));
    }

    private Comparator<? super T> createComparator() {
        return new Comparator<T>() {
            @Override
            public int compare(T o1, T o2) {
                return sorter.compare(
                        descriptedChildren.getDescription(o1),
                        descriptedChildren.getDescription(o2));
            }
        };
    }

    public Filter getFilter() {
        return filter;
    }

    public Sorter getSorter() {
        return sorter;
    }
}
